package itsm.isperp.workflow.service;

import itsm.isperp.framework.core.context.ContextHolder;
import itsm.isperp.framework.utils.JsonBuilder;
import itsm.isperp.framework.web.request.JqGridFilterRuleOp;
import itsm.isperp.framework.web.request.JqGridRequest;
import itsm.isperp.module.entity.app.AppDict;
import itsm.isperp.module.entity.bpm.BpmNodeRule;
import itsm.isperp.module.entity.bpm.BpmProcessSetting;
import itsm.isperp.module.service.app.AppDictService;
import itsm.isperp.module.service.bpm.BpmNodeRuleService;
import itsm.isperp.module.service.bpm.BpmProcessSettingService;
import itsm.isperp.module.service.bpm.act.BpmActService;
import itsm.isperp.workflow.engine.diagram.CustomerProcessDiagramGenerator;
import itsm.isperp.workflow.graph.ShapeMeta;
import itsm.isperp.workflow.utils.BpmUtils;

import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.editor.language.json.converter.BpmnJsonConverter;
import org.activiti.engine.HistoryService;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.impl.RepositoryServiceImpl;
import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.DeploymentBuilder;
import org.activiti.engine.repository.Model;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.repository.ProcessDefinitionQuery;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.DocumentException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

/**
 * 
 * 流程定义服务
 * 
 * @author lizx
 * @date 2013-7-1
 * @since 1.0
 * 
 */
@Service
public class BpmProcessDefinitaionService {

	@Autowired
	RepositoryService repositoryService;

	@Autowired
	RuntimeService runtimeService;

	@Autowired
	BpmActService bpmActService;

	@Autowired
	BpmNodeRuleService bpmNodeRuleService;

	@Resource
	ProcessEngineConfiguration processEngineConfiguration;

	@Autowired
	HistoryService historyService;

	@Autowired
	BpmBpmnModelService bpmBpmnModelService;

	/**
	 * 获取流程图形的字节码数据
	 * 
	 * @param processDefinitionId
	 *            流程定义id
	 * @return 流程图形的字节码数据
	 * @throws IOException
	 *             图形文件读取错误时
	 */
	public byte[] processImage(String processDefinitionId) throws IOException {
		// ProcessDefinitionEntity ent = (ProcessDefinitionEntity)
		// ((RepositoryServiceImpl) repositoryService)
		// .getDeployedProcessDefinition(processDefinitionId);

		BpmnModel bpmnModel = repositoryService
				.getBpmnModel(processDefinitionId);

		InputStream resourceAsStream = new CustomerProcessDiagramGenerator()
				.generateDiagram(bpmnModel, Collections.<String> emptyList(),
						Collections.<String> emptyList(), null);

		BufferedImage buffImg = ImageIO.read(resourceAsStream);

		ByteArrayOutputStream out = new ByteArrayOutputStream();
		ImageIO.write(buffImg, "png", out);
		byte[] bs = out.toByteArray();
		out.close();
		resourceAsStream.close();
		return bs;
	}

	/**
	 * 根据流程key获取流程实体接口定义
	 * 
	 * @param key
	 *            流程key
	 * @return 流程实体接口定义
	 */
	public ProcessDefinition getProcessDefinitionByKey(String key) {
		return repositoryService.createProcessDefinitionQuery()
				.processDefinitionKey(key).latestVersion().active()
				.singleResult();
	}

	/**
	 * 根据部署id获取模型
	 * 
	 * @param deploymentId
	 *            部署id
	 * @return 模型
	 */
	public Model getModelByDeploymentId(String deploymentId) {
		return repositoryService.createModelQuery().deploymentId(deploymentId)
				.orderByLastUpdateTime().desc().singleResult();
	}

	/**
	 * 根据流程定义id获取流程定义实体
	 * 
	 * @param processDefinitionId
	 *            流程定义id
	 * @return 流程定义实体
	 */
	public ProcessDefinitionEntity getProcessDefinitionEntityById(
			String processDefinitionId) {
		return (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService)
				.getDeployedProcessDefinition(processDefinitionId);

	}

	public ActivityImpl getActivityImpl(String processDefinitionId,
			String nodeId) {

		ProcessDefinitionEntity def = this
				.getProcessDefinitionEntityById(processDefinitionId);
		List<ActivityImpl> activitiList = def.getActivities();
		ActivityImpl currentActivityImpl = null;
		FINDACT: for (ActivityImpl activityImpl : activitiList) {
			String id = activityImpl.getId();

			if (id.equals(nodeId)) {
				currentActivityImpl = activityImpl;
				break FINDACT;
			}
		}

		return currentActivityImpl;
	}

	/**
	 * 根据流程定的key获取流程定义实体
	 * 
	 * @param key
	 *            流程定义key
	 * @return 流程实体
	 */
	public ProcessDefinitionEntity getProcessDefinitionEntityByKey(String key) {

		return (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService)
				.getDeployedProcessDefinition(this.getProcessDefinitionByKey(
						key).getId());

	}

	public ProcessDefinitionEntity getProcessDefinitionEntityByProcessInstanceId(
			String processInstanceId) {

		HistoricProcessInstance oldPi = this.historyService
				.createHistoricProcessInstanceQuery()
				.processInstanceId(processInstanceId).singleResult();
		return getProcessDefinitionEntityByProcessDefinitionId(oldPi
				.getProcessDefinitionId());

	}

	public ProcessDefinitionEntity getProcessDefinitionEntityByProcessDefinitionId(
			String processDefinitionId) {
		return (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService)
				.getDeployedProcessDefinition(processDefinitionId);

	}

	/**
	 * 根据流程key获取流程图形摘要
	 * 
	 * @param key
	 *            流程key
	 * @return 流程图形摘要
	 * @throws IOException
	 *             无法读取文档时抛出
	 * @throws DocumentException
	 *             文档转换错误时抛出
	 */
	public ShapeMeta getProcessDefinitionShapeMetaByKey(String key)
			throws IOException, DocumentException {

		return getProcessDefinitionShapeMeta(getProcessDefinitionByKey(key));
	}

	/**
	 * 根据刘定义id获取流程定义图形摘要
	 * 
	 * @param processDefinitionId
	 *            流程定义id
	 * @return 流程图形摘要
	 * @throws IOException
	 *             无法读取文档时抛出
	 * @throws DocumentException
	 *             文档转换错误时抛出
	 */
	public ShapeMeta getProcessDefinitionShapeMeta(String processDefinitionId)
			throws IOException, DocumentException {
		return getProcessDefinitionShapeMeta(repositoryService
				.createProcessDefinitionQuery()
				.processDefinitionId(processDefinitionId).singleResult());
	}

	/**
	 * 根据流程定义接口实体获取流程图形摘要，如果流程未部署返回null. <b>字节编码为utf-8</b>
	 * 
	 * 
	 * @param processDefinition
	 *            流程定义实体接口
	 * @return 流程图形摘要
	 * @throws IOException
	 *             无法读取文档时抛出
	 * @throws DocumentException
	 *             文档转换错误时抛出
	 */
	public ShapeMeta getProcessDefinitionShapeMeta(
			ProcessDefinition processDefinition) throws IOException,
			DocumentException {
		// 流程为部署则为空返回null
		if (StringUtils.isEmpty(processDefinition.getDeploymentId())) {
			return null;
		}

		String resourceName = processDefinition.getResourceName();
		InputStream resourceAsStream = repositoryService.getResourceAsStream(
				processDefinition.getDeploymentId(), resourceName);

		ByteArrayOutputStream outStream = new ByteArrayOutputStream();
		byte[] data = new byte[4096];
		int count = -1;

		String defXml = null;
		ShapeMeta shapeMeta = null;

		while ((count = resourceAsStream.read(data, 0, 4096)) != -1)
			outStream.write(data, 0, count);
		data = null;
		defXml = new String(outStream.toByteArray(), "UTF-8");
		shapeMeta = BpmUtils.transGraph(defXml);
		outStream.close();
		return shapeMeta;
	}

	/**
	 * 保存流程分支节点的设定
	 * 
	 * @param defId
	 *            流程定义id
	 * @param deployId
	 *            部署id
	 * @param forkNode
	 *            分支节点
	 * @param map
	 *            变量
	 * @throws IOException
	 *             无法读取文档时抛出
	 * @throws DocumentException
	 *             文档转换错误时抛出
	 */
	public void saveCondition(String defId, String deployId, String forkNode,
			Map<String, String> map) throws IOException, DocumentException {

		String defXml = this.bpmActService
				.getProcessDefinitionXmlByDeployId(deployId);

		for (String k : map.keySet()) {

			BpmNodeRule ruleModel = new BpmNodeRule();
			ruleModel.setId(k);
			ruleModel.setActReProcdefId(defId);
			ruleModel.setRuleContent(map.get(k));

			this.bpmNodeRuleService.saveOrUpdate(ruleModel);

		}
		defXml = BpmUtils.setCondition(forkNode, map, defXml);
		byte[] btyesXml = defXml.getBytes("UTF-8");

		this.bpmActService.updateDefXml(deployId, btyesXml);
		((ProcessEngineConfigurationImpl) this.processEngineConfiguration)
				.getDeploymentManager().getProcessDefinitionCache()
				.remove(defId);
	}

	public void updateMultiUserTaskCondition(final String defId,
			final String deployId, final String sourceNode,
			final String strLoop, final String strComp) throws IOException,
			DocumentException {

		String defXml = this.bpmActService
				.getProcessDefinitionXmlByDeployId(deployId);

		defXml = BpmUtils.setMultiUserTaskCondition(sourceNode, strLoop,
				strComp, defXml);

		HashMap<String, String> map = new HashMap<String, String>();
		map.put("strComp", strComp);
		map.put("strLoop", strLoop);
		BpmNodeRule ruleModel = new BpmNodeRule();
		ruleModel.setId(sourceNode);
		ruleModel.setActReProcdefId(defId);
		ruleModel.setRuleContent(JsonBuilder.buildNormalBinder().toJson(map));

		this.bpmNodeRuleService.saveOrUpdate(ruleModel);

		byte[] btyesXml = defXml.getBytes("UTF-8");

		this.bpmActService.updateDefXml(deployId, btyesXml);
		((ProcessEngineConfigurationImpl) this.processEngineConfiguration)
				.getDeploymentManager().getProcessDefinitionCache()
				.remove(defId);
	}
	
	public void deployNewProcess(String processName,String processKey) throws JsonProcessingException, XMLStreamException, IOException
	{
		String text ="<?xml version=\"1.0\" encoding=\"UTF-8\"?><definitions xmlns=\"http://www.omg.org/spec/BPMN/20100524/MODEL\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:activiti=\"http://activiti.org/bpmn\" xmlns:bpmndi=\"http://www.omg.org/spec/BPMN/20100524/DI\" xmlns:omgdc=\"http://www.omg.org/spec/DD/20100524/DC\" xmlns:omgdi=\"http://www.omg.org/spec/DD/20100524/DI\" typeLanguage=\"http://www.w3.org/2001/XMLSchema\" expressionLanguage=\"http://www.w3.org/1999/XPath\" targetNamespace=\"http://www.activiti.org/processdef\"><process id=\""+processKey+"\" isExecutable=\"true\"><extensionElements></extensionElements></process></definitions>";
		String resourceName=processName;
		
		BpmProcessSettingService bpmProcessSettingService = ContextHolder.getSpringBean("bpmProcessSettingService");
		
		JqGridRequest request=new JqGridRequest();
		request.addFilterRule("act_re_procdef_key", JqGridFilterRuleOp.eq,  processKey);
		BpmProcessSetting bps=bpmProcessSettingService.findOneByRequest(request);
		if(bps==null)
		{
			BpmProcessSetting bpst=new BpmProcessSetting();
			bpst.setActReProcdefKey(processKey);
			bpst.setAreDomain(true);
			bpst.setUseForRelation(true);
			bpst.setProcessRelationTitleColumn("description");
			bpst.setDbEntityId("2808465657466790006");
			bpst.setFormStartUrl("individuation/individuation/edit");
			bpst.setViewUrl("individuation/individuation/view");
			bpmProcessSettingService.saveOrUpdate(bpst);
			
			
			AppDictService appDictService = ContextHolder.getSpringBean("appDictService");
			AppDict ad=new AppDict();
			ad.setCode("workflow/processRun/entry?processKey="+processKey);
			ad.setDescription("快速创建");
			ad.setEnable(true);
			ad.setName("新建"+processName);
			ad.setTypeCode("PORTAL_CVENTER_CONFIG");
			appDictService.saveOrUpdate(ad);
			
		}
		ProcessEngine processEngine = ContextHolder
				.getSpringBean("processEngine");

		DeploymentBuilder deploymentBuilder = processEngine
				.getRepositoryService().createDeployment().name(resourceName);

		if (!resourceName.contains("bpmn20.xml")) {
			resourceName += ".bpmn20.xml";
		}

		deploymentBuilder.addString(resourceName, text);
		Deployment deployment = deploymentBuilder.deploy();

		ProcessDefinition pd = repositoryService.createProcessDefinitionQuery()
				.deploymentId(deployment.getId()).orderByDeploymentId().desc()
				.singleResult();

		this.convertToModel(pd);
		
	}

	public void deploy(String resourceName, String pathname)
			throws IOException, UnsupportedEncodingException,
			XMLStreamException {

		File file = new File(pathname);
		FileInputStream fs = new FileInputStream(file);

		String text = IOUtils.toString(fs, Charset.forName("UTF-8"));

		ProcessEngine processEngine = ContextHolder
				.getSpringBean("processEngine");

		DeploymentBuilder deploymentBuilder = processEngine
				.getRepositoryService().createDeployment().name(resourceName);

		if (!resourceName.contains("bpmn20.xml")) {
			resourceName += ".bpmn20.xml";
		}

		deploymentBuilder.addString(resourceName, text);
		Deployment deployment = deploymentBuilder.deploy();

		ProcessDefinition pd = repositoryService.createProcessDefinitionQuery()
				.deploymentId(deployment.getId()).orderByDeploymentId().desc()
				.singleResult();

		this.convertToModel(pd);

	}

	/**
	 * 
	 * @param defId
	 * @throws XMLStreamException
	 * @throws IOException
	 * @throws JsonProcessingException
	 */
	public Model convertToModel(ProcessDefinition processDefinition)
			throws XMLStreamException, JsonProcessingException, IOException {

		InputStream bpmnStream = repositoryService.getResourceAsStream(
				processDefinition.getDeploymentId(),
				processDefinition.getResourceName());
		XMLInputFactory xif = XMLInputFactory.newInstance();
		InputStreamReader in = new InputStreamReader(bpmnStream, "UTF-8");
		XMLStreamReader xtr = xif.createXMLStreamReader(in);
		BpmnModel bpmnModel = new BpmnXMLConverter().convertToBpmnModel(xtr);

		bpmBpmnModelService.convertBpmnModel(bpmnModel);

		BpmnJsonConverter converter = new BpmnJsonConverter();
		ObjectNode modelNode = converter.convertToJson(bpmnModel);

		String n = modelNode.toString();

		Model modelData = repositoryService.newModel();
		modelData.setKey(processDefinition.getKey());
		modelData.setName(processDefinition.getResourceName());
		modelData.setCategory(processDefinition.getDeploymentId());
		modelData.setDeploymentId(processDefinition.getDeploymentId());

		ObjectNode modelObjectNode = new ObjectMapper().createObjectNode();
		modelObjectNode.put(ModelDataJsonConstants.MODEL_NAME,
				processDefinition.getName());
		modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, 1);
		modelObjectNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION,
				processDefinition.getDescription());
		modelData.setMetaInfo(modelObjectNode.toString());

		repositoryService.saveModel(modelData);

		repositoryService.addModelEditorSource(modelData.getId(),
				n.getBytes("utf-8"));

		return modelData;

	}

	public ProcessDefinitionQuery createProcessDefinitionQuery() {
		return repositoryService.createProcessDefinitionQuery();
	}
}
